package cn.xiaobai.tools;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

/**
 * todo 目前存在的问题就是, 如果存在第三方依赖打的包会找不到依赖无法启动
 *      也就是后续需要解决的如何将第三方依赖加入和引导加载第三方依赖
 * @Author yangdaji
 * @Date 2023/10/26
 * @PackageName cn.xiaobai.tools
 * @ClassName Packager
 */
public class Packager extends AbstractPackager {

    private static final String MAIN_CLASS_ATTRIBUTE = "Main-Class";

    private static final String START_CLASS_ATTRIBUTE = "Start-Class";

    private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication";

    private static final String SPRING_BOOT_LOADER = "org.springframework.boot.loader.JarLauncher";
    private static final String XIAOBAI_BOOT_LOADER = "cn.xiaobai.loader.JarLauncher";

    private static final String XIAOBAI_BOOT_LOADER_JAR = "META-INF/loader/xiaobai-boot-loader.jar";

    private static final String BOOT_INFO_CLASSES = "BOOT-INF/classes/";
    private static final String BOOT_INFO_LIB = "BOOT-INF/lib/";

    private String mainClass;

    private final File source;


    public Packager(File source) {
        this.source = source;
    }

    public void setMainClass(String mainClass) {
        this.mainClass = mainClass;
    }

    public File getSource() {
        return source;
    }


    /**
     * 重新打包, 设置引导类到jar 包中, 即在当前插件的 META-INF/loader 目录下查找引导依赖, 默认使用自定义
     * 如果有外部引入, 则使用外部的
     */
    public void rePackager() throws IOException {
        File sourceFile = getSource();
        // 创建备份
        File workingSource = getBackupFile();
        // rename back
        sourceFile.renameTo(workingSource);
        // 删除旧文件, 则目录将作为输出
        sourceFile.delete();

        try (JarFile sourcJarFile = new JarFile(workingSource)) {
            write(sourcJarFile, sourceFile, getLoaderClass());
        }

    }

    protected File getBackupFile() {
        File sourceFile = getSource();
        return new File(sourceFile.getParentFile(), sourceFile.getName() + ".original");
    }


    private File getLoaderClass() {
        URL resource = getClass().getClassLoader().getResource(XIAOBAI_BOOT_LOADER_JAR);
        String file = resource.getFile().replace("\\", "/");
        String sourceJarFile = file.substring(0, file.indexOf("!"));
        return new File(sourceJarFile.substring(sourceJarFile.indexOf("/")));
    }


    @Override
    protected void buildManifest(JarFile jarFile) throws IOException {
        Manifest manifest = jarFile.getManifest();
        addMainAndStartAttributes(jarFile, manifest);
    }

    @Override
    protected String getLoaderPath() {
        return Packager.XIAOBAI_BOOT_LOADER_JAR;
    }

    @Override
    protected String getClassOutPath() {
        return BOOT_INFO_CLASSES;
    }

    @Override
    protected String getClassLibPath() {
        return BOOT_INFO_LIB;
    }

    @Override
    protected ClassLoader getURLClassLoad() throws IOException {
        return new URLClassLoader((new URL[]{source.toURI().toURL()}));
    }

    private void addMainAndStartAttributes(JarFile source, Manifest manifest) throws IOException {
        String mainClass = getMainClass(source, manifest);
        // todo 引导类先不改, 启动类需要去匹配, 可以通过自定义引导类,或者使用 springboot loader 依赖中的引导类
        manifest.getMainAttributes().putValue(MAIN_CLASS_ATTRIBUTE, "cn.xiaobai.loader.JarLauncher");
//        manifest.getMainAttributes().putValue(MAIN_CLASS_ATTRIBUTE, "org.springframework.boot.loader.JarLauncher");
        manifest.getMainAttributes().putValue(START_CLASS_ATTRIBUTE, mainClass);
    }

    private String getMainClass(JarFile source, Manifest manifest) throws IOException {
        if (this.mainClass != null) {
            return this.mainClass;
        }
        // 如果手动指定, 则使用指定, 否则自动匹配
        String attributeValue = manifest.getMainAttributes().getValue(MAIN_CLASS_ATTRIBUTE);
        if (attributeValue != null) {
            return attributeValue;
        }
        return findMainMethodWithTimeoutWarning(source);
    }

    private String findMainMethodWithTimeoutWarning(JarFile source) throws IOException {
        return findMainMethod(source);
    }


    private String findMainMethod(JarFile source) throws IOException {
        return MainClassFinder.findSingleMainClass(source, SPRING_BOOT_APPLICATION_CLASS_NAME);
    }

}
